package com.meshnetics.gb.stack.mac.primitives;

import com.meshnetics.gb.stack.core.event.StackEvent;
import com.meshnetics.gb.stack.core.event.StackEventType;
import com.meshnetics.gb.stack.core.event.StackIndication;
import com.meshnetics.gb.stack.core.layer.StackLayer;
import com.meshnetics.gb.stack.mac.RadioMACStackLayer;
import com.meshnetics.gb.stack.mac.type.DataAddress;
import com.meshnetics.gb.stack.mac.type.MACStatus;
import com.meshnetics.gb.stack.mac.type.TypeDataAddress;
import com.meshnetics.gb.stack.mac.type.TypeMACStatus;
import com.meshnetics.gb.type.impl.ByteUtils;
import com.meshnetics.gb.type.impl.StringUtils;
import com.meshnetics.gb.type.impl.zcl.TypeUInt;
import com.meshnetics.gb.type.impl.zcl.UInt;

import java.nio.ByteBuffer;

/**
 * WRITE DESCRIPTION HERE
 *
 * @author AKovylin
 */
public class MACCommStatusIndication extends StackIndication implements MACReplyOnCommand {
    public static final byte CODE = 0x5a;

    private static final byte[] ALLOWED_STATUSES = new byte[]{
            MACStatus.SUCCESS.getCode(),
            MACStatus.TRANSACTION_OVERFLOW.getCode(),
            MACStatus.TRANSACTION_EXPIRED.getCode(),
            MACStatus.CHANNEL_ACCESS_FAILURE.getCode(),
            MACStatus.NO_ACK.getCode(),
            MACStatus.UNAVAILABLE_KEY.getCode(),
            MACStatus.FRAME_TOO_LONG.getCode(),
            MACStatus.FAILED_SECURITY_CHECK.getCode(),
            MACStatus.INVALID_PARAMETER.getCode(),
    };

    private final DataAddress srcAddr;
    private final DataAddress dstAddr;
    private final MACStatus status;

    public MACCommStatusIndication(ByteBuffer bb, RadioMACStackLayer layer) {
        super("macCommStatusIndication", StackEventType.LME_TYPE, null, layer);
        ByteUtils.ensureNotNull(bb);
        ByteUtils.ensureCapacity(bb, 3);
        UInt panId = (UInt) TypeUInt.UINT_16.deserialize(bb);
        byte addrMode = bb.get();
        if (addrMode == TypeDataAddress.ADDR_MODE_PANID) {
            this.srcAddr = new DataAddress(panId);
        } else if (addrMode == TypeDataAddress.ADDR_MODE_SHORT) {
            this.srcAddr = new DataAddress(panId, (UInt) TypeUInt.UINT_16.deserialize(bb));
        } else if (addrMode == TypeDataAddress.ADDR_MODE_LONG) {
            this.srcAddr = new DataAddress(panId, (UInt) TypeUInt.UINT_64.deserialize(bb));
        } else {
            throw new IllegalArgumentException("invalid srcAddrMode " + StringUtils.toHexString(addrMode));
        }
        ByteUtils.ensureCapacity(bb, 1);
        addrMode = bb.get();
        if (addrMode == TypeDataAddress.ADDR_MODE_PANID) {
            this.dstAddr = new DataAddress(panId);
        } else if (addrMode == TypeDataAddress.ADDR_MODE_SHORT) {
            this.dstAddr = new DataAddress(panId, (UInt) TypeUInt.UINT_16.deserialize(bb));
        } else if (addrMode == TypeDataAddress.ADDR_MODE_LONG) {
            this.dstAddr = new DataAddress(panId, (UInt) TypeUInt.UINT_64.deserialize(bb));
        } else {
            throw new IllegalArgumentException("invalid dstAddrMode " + StringUtils.toHexString(addrMode));
        }
        this.status = (MACStatus) TypeMACStatus.TYPE.deserialize(bb);
        Utils.checkStatus(status, ALLOWED_STATUSES);
        if (bb.remaining() > 0) {
            throw new IllegalArgumentException("unexpected content of ByteBuffer, " + bb.remaining() + " bytes left");
        }
    }

    public MACCommStatusIndication(DataAddress srcAddr, DataAddress dstAddr, MACStatus status) {
        this(srcAddr, dstAddr, status, null);
    }

    public MACCommStatusIndication(DataAddress srcAddr, DataAddress dstAddr, MACStatus status, StackLayer source) {
        super("macCommStatusIndication", StackEventType.LME_TYPE, null, source);
        if (srcAddr == null) {
            throw new NullPointerException("srcAddr is null");
        }
        if (dstAddr == null) {
            throw new NullPointerException("dstAddr is null");
        }
        if (status == null) {
            throw new NullPointerException("status is null");
        }
        Utils.checkStatus(status, ALLOWED_STATUSES);
        this.srcAddr = srcAddr;
        this.dstAddr = dstAddr;
        this.status = status;
    }

    public DataAddress getSrcAddr() {
        return srcAddr;
    }

    public DataAddress getDstAddr() {
        return dstAddr;
    }

    public MACStatus getStatus() {
        return status;
    }

    public boolean isReplyOn(StackEvent se) {
        boolean res = false;
        if (se instanceof MACAssociateResponse) {
            MACAssociateResponse resp = (MACAssociateResponse) se;
            if (srcAddr.equals(new DataAddress(srcAddr.getPanIdType(), resp.getDeviceAddressType())) ||
                    srcAddr.equals(new DataAddress(srcAddr.getPanIdType(), resp.getAssocShortAddrType()))) {
                res = true;
            }
        } else if (se instanceof MACOrphanResponse) {
            MACOrphanResponse resp = (MACOrphanResponse) se;
            if (srcAddr.equals(new DataAddress(srcAddr.getPanIdType(), resp.getOrphanAddressType())) ||
                    srcAddr.equals(new DataAddress(srcAddr.getPanIdType(), resp.getShortAddressType()))) {
                res = true;
            }
        }
        return res;
    }

    public String toString() {
        return getName() + '(' + srcAddr + ',' + dstAddr + ',' + status + ')';
    }
}
